package ui;

import java.awt.Frame;
import java.awt.Rectangle;
import java.awt.Font;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Component;
import java.awt.Event;
import java.awt.TextField;
import java.awt.List;
import java.awt.Choice;

/**
 * Extends the standard AWT dialog to provide field validation. It uses
 * Microsoft's DialogLayout manager to place the components.
 * 
 * @version 1.0
 * @author Hans U. Gerber (<a
 *         href="mailto:gerber@ifh.ee.ethz.ch">gerber@ifh.ee.ethz.ch</a>)
 */
public class Dialog extends java.awt.Dialog {
	protected Frame parent;
	protected DialogLayout layout;
	private boolean shouldBeModal;

	public Dialog(Frame parent, Rectangle rect, String title, boolean modal) {
		// Modal dialogs are broken in the AWT. A call to show() does not block
		// on all platforms. As a quick fix, I disabled the parent frame when
		// showing
		// the dialog and re-enabled the parent when the dialog went away.
		// This worked fine with Internet Explorer 3.0, even with a modal dialog
		// open, the IE menu and toolbar were still active, the IE window could
		// even be minimized and took the dialog with it.
		// But in IE 3.01, MS obviously changed the behavior of modal dialogs:
		// show() does still not block, but the IE window is now completely
		// disabled, including the system menu. What's worse, when my dialog
		// goes
		// away, the IE window is no longer active, but the next application
		// window
		// on the screen. I tried requestFocus(), but this produced nasty
		// app window flickering.
		// I observed that I can get back the old behavior if I create all
		// dialogs
		// non-modal and then disable/enable the parent explicitely.

		// Make dialog non-modal (parent will be disabled explicitely in
		// show());
		// remember the state of the modal flag:
		super(parent, title, false);
		shouldBeModal = modal;
		this.parent = parent;
		// Since a given font may not be supported across all platforms, it
		// is safe to modify only the size of the font, not the typeface.
		Font OldFnt = getFont();
		if (OldFnt != null) {
			Font NewFnt = new Font(OldFnt.getName(), OldFnt.getStyle(), 8);
			setFont(NewFnt);
		}
		// All position and sizes are in dialog logical units, so we use a
		// DialogLayout as our layout manager.
		layout = new DialogLayout(this, rect.width, rect.height);
		setLayout(layout);
		addNotify();
		Dimension size = layout.getDialogSize();
		Insets insets = insets();
		resize(insets.left + size.width + insets.right, insets.top
				+ size.height + insets.bottom);
		move(rect.x, rect.y);
	}

	/**
	 * Finds the top-level frame in the ancestry of startComponent, if there is
	 * one.
	 */
	public static Frame findFirstFrame(Component startComponent) {
		Component c = startComponent;
		while (c != null) {
			if (c instanceof Frame)
				break;
			c = c.getParent();
		}
		return (Frame) c;
	}

	/**
	 * This method is overridden just to close the dialog window on receiving
	 * the WINDOW_DESTROY message. All other messages are passed along
	 * unhandled.
	 */
	public boolean handleEvent(Event evt) {
		switch (evt.id) {
		case Event.WINDOW_DESTROY:
			onCancel();
			return true;
		default:
			return super.handleEvent(evt);
		}
	}

	/**
	 * Adds a component to the dialog, shaping it at the same time.
	 */
	public Component addComponent(Component component, Rectangle rect) {
		Component retVal = super.add(component);
		layout.setShape(component, rect);
		return retVal;
	}

	/**
	 * Called to validate and accept the contents of the dialog's components
	 * when the user selects "OK" or "Apply" in the dialog. If all contents are
	 * found valid, their values are transferred to the components' data models.
	 * If the dialog is modal, it goes away after this.
	 * 
	 * @return <code>null</code> if all validatable components are valid, an
	 *         error message otherwise
	 */
	public String onApply() {
		// First, check if all validatable components
		// are valid; but do not yet accept any data.
		// Only after all components have been checked
		// and their values found valid then accept them.
		String validateString = check();
		if (validateString == null) {
			accept();
			if (shouldBeModal) {
				dispose();
			}
		}
		return validateString;
	}

	/**
	 * To be called when the user leaves the dialog with "Cancel".
	 */
	public void onCancel() {
		dispose();
	}

	/**
	 * Checks the validity of the contents of all validatable components in the
	 * dialog. If any component is found invalid, it receives the focus and the
	 * error message of the offending component is returned.
	 * 
	 * @return <code>null</code> if all components are valid, an error message
	 *         otherwise
	 */
	public String check() {
		String validateMsg = null;
		for (int i = 0; i < countComponents(); i++) {
			Component component = getComponent(i);
			if (component instanceof Validatable) {
				validateMsg = ((Validatable) component).check();
				if (validateMsg != null) {
					component.requestFocus();
					if (component instanceof TextField) {
						((TextField) component).selectAll();
					}
					return validateMsg;
				}
			}
		}
		return null;
	}

	/**
	 * Accepts the contents from all validatable components in the dialog and
	 * updates their data models.
	 */
	public void accept() {
		for (int i = 0; i < countComponents(); i++) {
			Component component = getComponent(i);
			if (component instanceof Validatable) {
				Validatable field = (Validatable) component;
				field.accept();
			}
		}
	}

	/**
	 * Just a fix for an AWT bug: The Windows implementation does not handle
	 * modal dialogs. As a quick workaround, I disable the parent window as long
	 * as the dialog is shown.
	 */
	public void show() {
		if (shouldBeModal) {
			parent.disable();
		}
		super.show();
	}

	/**
	 * A fix for an AWT bug: The Windows implementation does not handle modal
	 * dialogs. As a quick workaround, I disable the parent window as long as
	 * the dialog is shown. I have to reenable the parent here.
	 */
	public void dispose() {
		super.dispose();
		if (shouldBeModal) {
			parent.enable();
		}
	}
}
